package serv_impl

import (
	"context"
	"net"
	"time"

	"atomgit.com/openlinksaas/proto-gen-go.git/net_proxy_api"
	"github.com/google/uuid"
)

type ConnItem struct {
	ConnId       string
	TargetConn   net.Conn
	OutChan      chan []byte
	LastReadTime int64
}

func (apiImpl *NetProxyApiImpl) CreateConn(ctx context.Context, req *net_proxy_api.CreateConnRequest) (*net_proxy_api.CreateConnResponse, error) {
	tunnelValue, ok := apiImpl.tunnelMap.Load(req.TunnelId)
	if !ok {
		return &net_proxy_api.CreateConnResponse{
			Code:   net_proxy_api.CreateConnResponse_CODE_NO_TUNNEL,
			ErrMsg: "通道不存在",
		}, nil
	}
	tunnelItem := tunnelValue.(*TunnelItem)

	nowTime := time.Now().Unix()
	tunnelItem.LastReadTime = nowTime
	apiImpl.tunnelMap.Store(tunnelItem.TunnelId, tunnelItem)

	connId := uuid.NewString()
	conn, err := net.DialTimeout("tcp", tunnelItem.EpCfg.Addr, 10*time.Second)
	if err != nil {
		return nil, err
	}
	outChan := make(chan []byte, 100)
	go apiImpl.readConn(conn, outChan)
	apiImpl.connMap.Store(connId, &ConnItem{
		ConnId:       connId,
		TargetConn:   conn,
		OutChan:      outChan,
		LastReadTime: nowTime,
	})

	return &net_proxy_api.CreateConnResponse{
		Code:   net_proxy_api.CreateConnResponse_CODE_OK,
		ConnId: connId,
	}, nil
}

func (apiImpl *NetProxyApiImpl) readConn(conn net.Conn, outChan chan<- []byte) {
	defer func() {
		recover()
	}()

	for {
		buf := make([]byte, 65*1024)
		n, err := conn.Read(buf)
		if n > 0 {
			outChan <- buf[:n]
		}
		if err != nil {
			break
		}
	}
}

func (apiImpl *NetProxyApiImpl) WriteConn(ctx context.Context, req *net_proxy_api.WriteConnRequest) (*net_proxy_api.WriteConnResponse, error) {
	defer func() {
		recover()
	}()

	_, ok := apiImpl.tunnelMap.Load(req.TunnelId)
	if !ok {
		return &net_proxy_api.WriteConnResponse{
			Code:   net_proxy_api.WriteConnResponse_CODE_NO_TUNNEL,
			ErrMsg: "通道不存在",
		}, nil
	}

	connValue, ok := apiImpl.connMap.Load(req.ConnId)
	if !ok {
		return &net_proxy_api.WriteConnResponse{
			Code:   net_proxy_api.WriteConnResponse_CODE_NO_CONN,
			ErrMsg: "连接不存在",
		}, nil
	}
	connItem := connValue.(*ConnItem)

	writed := 0
	for writed < len(req.Data) {
		n, err := connItem.TargetConn.Write(req.Data[writed:])
		if err != nil {
			return nil, err
		}
		writed += n
	}

	return &net_proxy_api.WriteConnResponse{
		Code: net_proxy_api.WriteConnResponse_CODE_OK,
	}, nil
}

func (apiImpl *NetProxyApiImpl) ReadConn(ctx context.Context, req *net_proxy_api.ReadConnRequest) (*net_proxy_api.ReadConnResponse, error) {
	defer func() {
		recover()
	}()

	_, ok := apiImpl.tunnelMap.Load(req.TunnelId)
	if !ok {
		return &net_proxy_api.ReadConnResponse{
			Code:   net_proxy_api.ReadConnResponse_CODE_NO_TUNNEL,
			ErrMsg: "通道不存在",
			Data:   []byte{},
		}, nil
	}

	connValue, ok := apiImpl.connMap.Load(req.ConnId)
	if !ok {
		return &net_proxy_api.ReadConnResponse{
			Code:   net_proxy_api.ReadConnResponse_CODE_NO_CONN,
			ErrMsg: "连接不存在",
			Data:   []byte{},
		}, nil
	}

	connItem := connValue.(*ConnItem)
	connItem.LastReadTime = time.Now().Unix()
	apiImpl.connMap.Store(req.ConnId, connItem)

	select {
	case data := <-connItem.OutChan:
		return &net_proxy_api.ReadConnResponse{
			Code: net_proxy_api.ReadConnResponse_CODE_OK,
			Data: data,
		}, nil
	case <-time.After(10 * time.Second):
		return &net_proxy_api.ReadConnResponse{
			Code: net_proxy_api.ReadConnResponse_CODE_OK,
			Data: []byte{},
		}, nil
	}
}
